// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//

#include <nebula/types/data_type.h>
#include <nebula/types/field.h>
#include <nebula/core/compare.h>
#include <nebula/util/hash_util.h>
#include <nebula/core/visit_type_inline.h>
#include <turbo/log/logging.h>
#include <nebula/types/type_traits.h>

namespace nebula {

    DataType::~DataType() {}

    bool DataType::equals(const DataType &other, bool check_metadata) const {
        return type_equals(*this, other, check_metadata);
    }

    bool DataType::equals(const std::shared_ptr<DataType> &other, bool check_metadata) const {
        if (!other) {
            return false;
        }
        return equals(*other.get(), check_metadata);
    }

    size_t DataType::hash() const {
        static constexpr size_t kHashSeed = 0;
        size_t result = kHashSeed;
        internal::hash_combine(result, this->fingerprint());
        return result;
    }

    std::string DataType::compute_fingerprint() const {
        // Default implementation returns empty string, signalling non-implemented
        // functionality.
        return "";
    }

    std::string DataType::compute_metadata_fingerprint() const {
        // Whatever the data type, metadata can only be found on child fields
        std::string s;
        for (const auto &child: children_) {
            // Add field name to metadata fingerprint so that the field names within
            // list and map types are included as part of the metadata. They are
            // excluded from the base fingerprint.
            s += child->name() + "=";
            s += child->metadata_fingerprint() + ";";
        }
        return s;
    }

    std::ostream &operator<<(std::ostream &os, const DataType &type) {
        os << type.to_string();
        return os;
    }

    namespace {

        struct PhysicalTypeVisitor {
            const std::shared_ptr<DataType> &real_type;
            std::shared_ptr<DataType> result;

            turbo::Status Visit(const DataType &) {
                result = real_type;
                return turbo::OkStatus();
            }

            template<typename Type, typename PhysicalType = typename Type::PhysicalType>
            turbo::Status Visit(const Type &type) {
                result = TypeTraits<PhysicalType>::type_singleton();
                return turbo::OkStatus();
            }
        };

    }  // namespace

    std::shared_ptr<DataType> GetPhysicalType(const std::shared_ptr<DataType> &real_type) {
        PhysicalTypeVisitor visitor{real_type, {}};
        KCHECK_OK(visit_type_inline(*real_type, &visitor));
        return std::move(visitor.result);
    }

    turbo::Status DataType::Accept(TypeVisitor *visitor) const {
        return visit_type_inline(*this, visitor);
    }
}  // namespace nebula
